package blockchain

import (
	"fmt"
	"crypto/sha256"
	"math/big"
	"math"
	"encoding/json"
	"encoding/hex"
	"encoding/binary"
	"bytes"
)

type BlockChain []*Block

type Block struct {
	Data string
	Diff uint8 //сложность
	PrevHash []byte
	CurrHash []byte
	Nonce uint64
}

const (
	DIFFICULTY = 16
)

func NewChain() BlockChain {
	return BlockChain{
		NewBlock("FirstBlock", []byte("GENESIS")),
	}
}

func NewBlock(data string, prevHash []byte) *Block {
	var block = &Block{
		Data: data,
		Diff: DIFFICULTY,
		PrevHash: prevHash,
	}
	block.CurrHash = HashSum(bytes.Join(
		[][]byte{
			[]byte(block.Data),
			ToBytes(uint64(block.Diff)),
			block.PrevHash,
		},
		[]byte{},
	))
	block.Nonce = ProodOfWork(block.CurrHash, block.Diff)
	return block
}

func HashSum(data []byte) []byte {
	var hash =sha256.Sum256(data)
	return hash[:]
} 

func ToBytes(num uint64) []byte {
	var buffer = new(bytes.Buffer)
	err :=binary.Write(buffer, binary.BigEndian, num)
	if err != nil {
		return nil
	}
	return buffer.Bytes()
}

const (
	DEBUG = true
)

func ProodOfWork(blockHash []byte, diff uint8) uint64 {
	var (
		target = big.NewInt(1)
		nonce = uint64(0)
		hash []byte
		intHash = big.NewInt(1)
	)


	target.Lsh(target, 256-uint(diff))
	for nonce < math.MaxUint64 {
		hash = HashSum(bytes.Join(
			[][]byte{
				ToBytes(nonce),
				blockHash,
			},
			[]byte{},
		))
		if DEBUG {
			fmt.Printf("\rMining: %s", hex.EncodeToString(hash))
		}
		intHash.SetBytes(hash)
		if intHash.Cmp(target) == -1 {
			if DEBUG {
				fmt.Println()
			}
			return nonce
		}
		nonce++
	}
	return nonce
}

func SerializeBlock(block *Block) string {
	jsonData, err := json.MarshalIndent(*block, "", "\t")
	if err != nil {
		return ""
	}
	return string(jsonData)
}

func DeserializeBlock(data string) *Block {
	var block = new(Block)
	err := json.Unmarshal([]byte(data), block)
	if err != nil {
		return nil
	}
	return block
}